package block

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"math/rand"
	"time"
)

const (
	EMPTY_BLOCK = "114514"
	// 区块可能绕圈
	CIRCLE         = -114514
	UNKNOWN_HEIGHT = -514114
)

type Block struct {
	// 前一个区块，用hash值表征
	Previous string
	// 挖空任务的solution
	Nonce uint64
	// 挖矿任务难度系数
	BitsThreshold string
	// 生成时间
	Birth string
	/*-------------以上为标准，以下为自定义----------------*/
	// 区块自己的身份/hash值
	SelfHash string
	// 区块所处的长度
	Height int64
}

type standardBlock struct {
	Previous string `json:"prev"`
	Time     string `json:"time"`
	Bits     string `json:"bits"`
	Nonce    uint64 `json:"nonce"`
}

func (b *Block) CalculateSelfHash() string {
	tmp := standardBlock{
		Previous: b.Previous,
		Time:     b.Birth,
		Bits:     b.BitsThreshold,
		Nonce:    b.Nonce,
	}
	text, err := json.Marshal(tmp)
	if err != nil {
		return b.SelfHash
	}
	// fmt.Println(string(text))
	return CalculateHash(text)
}

func (b *Block) Initialize(prevHash, difficulty string) {
	// 1
	b.Previous = prevHash
	// 2
	b.BitsThreshold = difficulty
	// 3
	rand.Seed(time.Now().UnixNano())
	b.Nonce = uint64(rand.Intn(100000000))
	// 4
	b.Birth = time.Now().Format("15:04:05")
	/*---------以上为标准成员-----------*/
	// 5
	b.SelfHash = b.CalculateSelfHash()
	// 6
	b.Height = UNKNOWN_HEIGHT
}

func (b *Block) IsValid() bool {
	// 1. 检查区块的完整性/是否被篡改
	if b.SelfHash != b.CalculateSelfHash() {
		return false
	}
	// 2. 检查工作量/挖矿任务
	raw := fmt.Sprintf("%s%d", b.Previous, b.Nonce)
	check := CalculateHash([]byte(raw))
	// fmt.Println(check)
	return check <= b.BitsThreshold
}

func CalculateHeight(notebook map[string]Block, blockID string) int64 {
	visited := make(map[string]struct{})
	return findHeightRecursive(notebook, visited, blockID)
}

// 返回节点block所处的长度（可能成环）,并记忆化
func findHeightRecursive(notebook map[string]Block, visited map[string]struct{}, blockID string) int64 {
	// 递归终点：空节点
	if blockID == EMPTY_BLOCK {
		return 0
	}

	// 递归终点：未记录节点
	current, exists := notebook[blockID]
	if !exists {
		return UNKNOWN_HEIGHT
	}

	// 递归终点：绕圈子/成环
	_, seen := visited[blockID]
	if seen {
		tmp := notebook[blockID]
		tmp.Height = CIRCLE
		notebook[blockID] = tmp
		return CIRCLE
	}

	// 每次递归：标记visited,访问前一个节点，记录当前高度
	visited[blockID] = struct{}{}
	defer delete(visited, blockID)
	preHeight := findHeightRecursive(notebook, visited, current.Previous)
	if preHeight == UNKNOWN_HEIGHT {
		tmp := notebook[blockID]
		tmp.Height = UNKNOWN_HEIGHT
		notebook[blockID] = tmp
		return UNKNOWN_HEIGHT
	}
	if preHeight == CIRCLE {
		tmp := notebook[blockID]
		tmp.Height = CIRCLE
		notebook[blockID] = tmp
		return CIRCLE
	}
	tmp := notebook[blockID]
	tmp.Height = preHeight + 1
	notebook[blockID] = tmp
	return notebook[blockID].Height
}

func traverseAllHeight(notebook map[string]Block) {
	visited := make(map[string]struct{})
	for hashID := range notebook {
		findHeightRecursive(notebook, visited, hashID)
	}
}

// 从缓冲区notebook找到最长的链
func GetLongestChainTail(notebook map[string]Block) Block {
	// 1. 得到所有节点的Height
	traverseAllHeight(notebook)
	// 2. 统计最长节点
	record := Block{}
	record.Initialize(EMPTY_BLOCK, "zzz")
	toBeDel := make([]string, 0)
	for hashID, b := range notebook {
		if b.Height == UNKNOWN_HEIGHT {
			continue
		}
		if b.Height == CIRCLE {
			toBeDel = append(toBeDel, hashID)
			continue
		}
		if record.Height == UNKNOWN_HEIGHT || record.Height < b.Height {
			record = b
		}
	}
	// 3. 删除成环节点
	for _, blockID := range toBeDel {
		delete(notebook, blockID)
	}
	return record
}

func CalculateHash(b []byte) (result string) {
	res := md5.Sum(b)                   //返回值：[Size]byte 数组
	result = hex.EncodeToString(res[:]) //对应的参数为：切片，需要将数组转换为切片。
	return result
}

func (b *Block) SetParent(prev string) {
	b.Previous = prev
	b.SelfHash = b.CalculateSelfHash()
}

func (b *Block) SetTime(time string) {
	b.Birth = time
	b.SelfHash = b.CalculateSelfHash()
}

func (b *Block) SetSolution(solution uint64) {
	b.Nonce = solution
	b.SelfHash = b.CalculateSelfHash()
}
